//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <winhack.h>
#include <elasys_server.h>
#include <driver.h>
#include <stdio.h>
#include <stdlib.h>

typedef void (CDECL *print_t)(const char *);

print_t _print = NULL;

#include "console.h"

#ifdef _DEBUG
#define Assert(x)   if (!((bool)(x))) { \
                        DebugBreak(); \
                    }
#else
#define Assert(x)
#endif

EXTERN_C ECode _CSystem_FindRunningObject(WString name,
            PInterface *pService);

extern IDeviceDriver *g_pConsoleChannelObj;

extern BOOL g_bUsingConsoleChannel;

Console *g_pConsole = NULL;

enum PROPERTY
{
    agent = 1,      //IDE agent, a.k.a. elagent
    debug,          //gdb
    console,        //comcons
    normal,         //normal connection
};

int ReCreateChannelForConsole()
{
    IDeviceDriver *manager = NULL;
    MemoryBuf_<8> ebbInData;
    MemoryBuf_<100> ebbOutData;
    ECode ec = 0;

    Int32 *pIn = (Int32 *)ebbInData.GetPayload();
    pIn[0] = (Int32)console;
    pIn[1] = 1; // setup
    ebbInData.SetUsed(8);

    if (manager == NULL) {
        ec = _CSystem_FindRunningObject(L"device:compoundchannelmanager", (IInterface **)&manager);
        if (FAILED(ec)) {
            printf("Retrieve compound channel manager failed\n");
            return -1;
        }
    }

    ec = manager->Control(
            /* [in] */ 0,
            /* [in] */ ebbInData,
            /* [out] */&ebbOutData,
            /* [out] */ NULL);
    if (FAILED(ec)) {
        printf("Create console channel failed\n");
        return -1;
    }
    wchar_t *pOut = (wchar_t *)ebbOutData.GetPayload();

    ec = _CSystem_FindRunningObject(pOut, (IInterface **)&g_pConsoleChannelObj);
    if (FAILED(ec)) {
        printf("Retrieve console channel failed\n");
        return -1;
    }

    return 0;
}

void CDECL WriteToConsole(const char *szMessage)
{
    uint32_t uLength = strlen(szMessage);

    if (0 == uLength) {
        return;
    }

    Assert(NULL != g_pConsole);

    uint32_t sent = 0;
    while (sent < uLength) {
        MemoryBuf pBuf((Byte *) szMessage + sent, uLength - sent);
        Int32 nWritten = 0;
        ECode ec = g_pConsole->Write(0, pBuf, &nWritten, NULL);
        if (FAILED(ec)) {
            break;
        }
        sent += nWritten;
    }
}

//
// The console driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateConsole(uint_t uDeviceNo, void *pvParameter)
{
    ECode ec;

    g_pConsole = new Console;
    if (NULL == g_pConsole) {
        Assert(0 && "There is not enough memory");
        return NULL;
    }

    // Initialize keyboard hardware
    ec = ((Console*)g_pConsole)->Initialize();
    if (FAILED(ec)) {
        delete g_pConsole;
        g_pConsole = NULL;
        return NULL;
    }

    _print = &WriteToConsole;

    g_pConsole->AddRef();

    return (IDeviceDriver *)g_pConsole;
}

ECode Console::Initialize()
{
    for (int i = 0; i < HISTORY_NUMBER; i++) {
        m_ppHistory[i] = (char*)malloc(sizeof(char) * HISTORY_MAX_LENGTH);
        if (NULL == m_ppHistory[i]) {
            return E_OUT_OF_MEMORY;
        }
        *m_ppHistory[i] = '\0';
    }

    m_bBuffer = TRUE;
    m_TempValue = 0;
    m_FirstRecord = 0;
    m_pOldPrint = _print;

    return NOERROR;
}

int Console::TranslateAscii(int ch)
{

    switch (ch) {
        case ASCII_LeftArrow:
            m_TempValue = 0x4B;
            return 0xE0;
        case ASCII_RightArrow:
            m_TempValue = 0x4D;
            return 0xE0;
        case ASCII_Insert:
            m_TempValue = 0x52;
            return 0xE0;
        case ASCII_Delete:
            m_TempValue = 0x53;
            return 0xE0;
        case ASCII_Home:
            m_TempValue = 0x47;
            return 0xE0;
        case ASCII_End:
            m_TempValue = 0x4F;
            return 0xE0;
        case ASCII_UpArrow:
            m_TempValue = 0x48;
            return 0xE0;
        case ASCII_DownArrow:
            m_TempValue = 0x50;
            return 0xE0;
        case ASCII_PageUp:
            m_TempValue = 0x49;
            return 0xE0;
        case ASCII_PageDown:
            m_TempValue = 0x51;
            return 0xE0;

        case ASCII_F1:
            m_TempValue = 0x3B;
            return 0x0;
        case ASCII_F2:
            m_TempValue = 0x3C;
            return 0x0;
        case ASCII_F3:
            m_TempValue = 0x3D;
            return 0x0;
        case ASCII_F4:
            m_TempValue = 0x3E;
            return 0x0;
        case ASCII_F5:
            m_TempValue = 0x3F;
            return 0x0;
        case ASCII_F6:
            m_TempValue = 0x40;
            return 0x0;
        case ASCII_F7:
            m_TempValue = 0x41;
            return 0x0;
        case ASCII_F8:
            m_TempValue = 0x42;
            return 0x0;
       case ASCII_F9:
            m_TempValue = 0x43;
            return 0x0;
        case ASCII_F10:
            m_TempValue = 0x44;
            return 0x0;
        case ASCII_F11:
            m_TempValue = 0x85;
            return 0xE0;
        case ASCII_F12:
            m_TempValue = 0x86;
            return 0xE0;

        case ASCII_At:
            m_TempValue = 0x3;
            return 0x0;

        default:
            break;
    }

    return ch;
}

//move the cursor forward with count steps
void Console::MoveCursorForward(UInt32 count)
{
	if (count == 0) {
		return;
	}
	char* pBuf = new char[count + 1];
	if (pBuf == NULL) {
		return;
	}
    for (UInt32 i = 0; i < count; i++) {
        pBuf[i] = '\b';
    }
    pBuf[count] = '\0';
    _print(pBuf);
}

//change the cursor shape
void Console::SetCursorType(int cursortype)
{
    // Not supported.
}

ECode Console::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || !pBuffer->GetCapacity()
        || (int)bytesToRead > pBuffer->GetCapacity()
        || 0 == bytesToRead) {
        return E_INVALID_ARGUMENT;
    }
    if (g_bUsingConsoleChannel) {
    	do {
        	if (g_pConsoleChannelObj != NULL) {
        		return Read0(u64Offset, bytesToRead, pBuffer, ppCompletionEvent);
        	}
        	Sleep(100); // Wait 0.1s for compound channel to initialize
    	} while (g_bUsingConsoleChannel);
    }
    Sleep(100); // Wait 0.1s to avoid 100% CPU usage.
    uint8_t *p = (uint8_t *)pBuffer->GetPayload();
    p[0] = 0;
    pBuffer->SetUsed(1);
    return NOERROR;
}

ECode Console::Read0(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }
    assert(bytesToRead > 0);

    if (!m_bBuffer) { //read one char or control key without line buffer
        int c = 0;
        if (m_TempValue) {
            c = m_TempValue;
            m_TempValue = 0;
        }
        else {
            MemoryBuf_<1> ezbbBuf;
            ECode ec = g_pConsoleChannelObj->Read(u64Offset,
                            1, &ezbbBuf, ppCompletionEvent);
            if (FAILED(ec)) { // considered as compound channel interrupted
                ReCreateChannelForConsole();
            }
            c = TranslateAscii(*(uint8_t *)ezbbBuf.GetPayload());
        }
        *(int*)pBuffer->GetPayload() = c;
        pBuffer->SetUsed(1);
        m_bBuffer = TRUE; //resume line buffer
    }
    else { //read chars with line buffer
        uint8_t ch = 0;
        UInt32 count = 0;
        UInt32 current = 0;
        UInt32 charcount = 0;
        Boolean b_Insert = TRUE;
        MemoryBuf ezbbBuf((Byte*)&ch, 1);

        char* str = (char*)pBuffer->GetPayload();
        memset(str, 0, 1);
        m_TempValue = 0;
        m_CurrentRecord = m_FirstRecord;

        do {
            //read one char from console device
        	ECode ec = g_pConsoleChannelObj->Read(u64Offset,
                        1, &ezbbBuf, ppCompletionEvent);
            if (FAILED(ec)) { // considered as compound channel interrupted
                if (ReCreateChannelForConsole() == -1) {
                	return ec;
                }
                Sleep(100); // Wait 0.1s for compound channel to initialize
                continue;
            }

            switch (ch) {
                case ASCII_BackSpace:
                    if (current && charcount) {
                        if (current == charcount) { //when cursor is at the end
                            str[current - 1] = 0;
                            MoveCursorForward(1);
                            _print(" ");
                            MoveCursorForward(1);
                        }
                        else {
                            memmove(str + current - 1, str + current,
                                                       charcount - current + 1);
                            MoveCursorForward(1);
                            _print(str + current - 1);
                            _print(" ");
                            MoveCursorForward(charcount - current + 1);
                        }
                        charcount--;
                        current--;
                    }
                    break;

                case ASCII_Home:       // goto beginning of string

                    MoveCursorForward(current);
                    current = 0;

                    break;

                case ASCII_Delete:       // delete character under cursor
                    if (current != charcount && charcount > 0) {
                        memmove(str + current, str + current + 1,
                                                          charcount - current);
                        charcount--;
                        _print(str + current);
                        _print(" ");
                        MoveCursorForward(charcount - current + 1);
                    }

                    break;

                case ASCII_End:         //goto end of string

                    if (current != charcount) {
                        _print(str + current);
                        current = charcount;
                    }

                    break;

                case KEY_ESC:

                    if (charcount) { //erase the existing command
                        MoveCursorForward(current); //Move cursor to the head

                        for (count = 0; count < charcount; count++) {
                            str[count] = ' ';
                        }
                        str[charcount] = '\0';
                        _print(str); //use blank to cover existing char

                        MoveCursorForward(charcount); //move cursor to the head

                        current = 0;
                        charcount = 0;
                        str[0] = '\0';
                    }

                    break;

                case ASCII_LeftArrow:            // move cursor left

                    if (current > 0) {
                        MoveCursorForward(1);
                        current--;
                    }

                    break;

                case ASCII_RightArrow:            //move cursor right

                    if (current != charcount) {
                        char c[2];
                        c[0] = str[current];
                        c[1] = '\0';
                        _print(c); //just print char int the cursor
                        current++;
                    }

                    break;

                case ASCII_UpArrow:       //get previous command from buffer

                    if (charcount) {
                        MoveCursorForward(current); //Move cursor to the head

                        for (count = 0; count < charcount; count++) {
                            str[count] = ' ';
                        }
                        str[charcount] = '\0';
                        _print(str); //use blank to cover existing char

                        MoveCursorForward(charcount); //move cursor to the head
                    }

                    //put the previous command into the buffer, if exist
                    if ('\0' != m_ppHistory[(HISTORY_NUMBER + m_CurrentRecord
                        - 1) % HISTORY_NUMBER][0]
                        && ((HISTORY_NUMBER + m_CurrentRecord - 1) %
                        HISTORY_NUMBER) != m_FirstRecord) {
                        if ((m_CurrentRecord == m_FirstRecord) &&
                            (0 == m_TempValue)) {
                            m_TempValue = 1;
                        }
                        else {
                            m_CurrentRecord = (HISTORY_NUMBER + m_CurrentRecord
                                               - 1) % HISTORY_NUMBER;
                        }
                    }
                    strcpy(str, m_ppHistory[m_CurrentRecord]);

                    charcount = strlen(str);
                    current = charcount;
                    if (str[0]) _print(str);

                    break;

                case ASCII_DownArrow:     //get next command from buffer

                    //put the next command into the buffer, if exist
                    if ('\0' == *m_ppHistory[m_FirstRecord]
                        || m_CurrentRecord == m_FirstRecord) {
                        continue;
                    }
                    else {
                        if (charcount) { //erase the existing command
                            // Move cursor to the head
                            MoveCursorForward(current);

                            for (count = 0; count < charcount; count++) {
                                str[count] = ' ';
                            }
                            str[charcount] = '\0';
                            // use blank to cover existing char
                            _print(str);

                            // move cursor to the head
                            MoveCursorForward(charcount);
                        }
                        m_CurrentRecord =
                            (m_CurrentRecord + 1) % HISTORY_NUMBER;
                    }
                    strcpy(str, m_ppHistory[m_CurrentRecord]);
                    charcount = strlen(str);
                    current = charcount;

                    if (str[0]) _print(str);

                    break;

                case KEY_CTL_C:

                    str[0] = '\n';
                    charcount = 1;
                    _print("\n"); //change line without erasing command

                    break;

                case ASCII_Insert: //when insert, change cursor shape

                    if (b_Insert) {
                        SetCursorType(SOLIDCURSOR);
                        b_Insert = FALSE;
                    }
                    else {
                        SetCursorType(NORMALCURSOR);
                        b_Insert = TRUE;
                    }

                    break;

                case ASCII_ENTER:            //end input

                    if (str[0]) {
                        if (charcount < (HISTORY_MAX_LENGTH - 1)) {
                            str[charcount] = '\0';
                        }
                        else {
                            str[HISTORY_MAX_LENGTH - 1] = '\0';
                        }

                        if (strcmp(str, m_ppHistory[m_FirstRecord])) {
                            m_FirstRecord =
                                (m_FirstRecord + 1) % HISTORY_NUMBER;
                            strcpy(m_ppHistory[m_FirstRecord], str);
                        }
                    }

                    if (charcount < (UInt32)bytesToRead) {
                        str[charcount++] = '\n';
                    }

                    _print("\n");

                    m_TempValue = 0;

                    break;

                default:          //insert character into string...

                    if ((ch >= 32 && ch <= 128) && (charcount <
                        (UInt32)bytesToRead)) {
                        if (b_Insert && current != charcount) {
                            str[charcount + 1] = '\0';
                            for (count = charcount; count > current; count--)
                                str[count] = str[count - 1];
                            str[current++] = ch;
                            _print(str + current - 1);
                            MoveCursorForward(charcount - current + 1);
                            charcount++;
                        }
                        else {
                            if (current == charcount) {
                                charcount++;
                                str[charcount] = '\0';
                            }
                            char c[2];
                            str[current++] = ch;
                            c[0] = ch;
                            c[1]= '\0';
                            _print(c);
                        }
                    }

                    break;
            }
        } while (ch != ASCII_ENTER && ch != KEY_CTL_C &&
            charcount < (UInt32)bytesToRead);
        SetCursorType(NORMALCURSOR); //set the cursor shape normal
        pBuffer->SetUsed(charcount);
    }

    return NOERROR;
}

ECode Console::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (g_bUsingConsoleChannel && g_pConsoleChannelObj != NULL) {
        ECode ec = g_pConsoleChannelObj->Write(u64Offset, buffer,
                pBytesWritten, ppCompletionEvent);
        if (FAILED(ec)) { // considered as compound channel interrupted
            ReCreateChannelForConsole();
        }
    }
    else {
        *pBytesWritten = buffer.GetUsed();
    }

    return NOERROR;
}

ECode Console::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if ((nControlCode < 0) || (nControlCode > 6)
        || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    // Set the Cursor's position
    if (0 == nControlCode) {
        // TODO:
        return NOERROR;
    }

    // Get the Cursor's position
    if (1 == nControlCode) {
        if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = 0;//m_uCursorPosition;

        pOutBuffer->SetUsed(sizeof(uint32_t));

        return NOERROR;
    }

    // Clear the screen
    if (2 == nControlCode) {
        // TODO:
        return NOERROR;
    }

    // Set the cursor shape
    if (3 == nControlCode) {
        // TODO:
        return NOERROR;
    }

    // Get the screen columns;
    if (4 == nControlCode) {
        if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = 80; // m_uScreenColumn;
        pOutBuffer->SetUsed(sizeof(uint32_t));

        return NOERROR;
    }

    // Get the screen rows;
    if (5 == nControlCode) {

        if (inBuffer.IsNullOrEmpty() || pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = 25; // m_uScreenRow;
        pOutBuffer->SetUsed(sizeof(uint32_t));

        return NOERROR;
    }

    if (6 == nControlCode) {
        return NOERROR;
    }

    return E_NOT_IMPLEMENTED;
}
